///////////////////////////////////////////////////////////////////////////////
//	FILENAME:	eTBookmark.m 
//	SUMMARY:	Implementation of the "brains" behind eText bookmarks
//	SUPERCLASS:	Object
//	INTERFACE:	None
//	PROTOCOLS:	<Annotation,HTMDSupport,ASCIISupport,LaTeXSupport,Tool,
//				InspectableTarget>
//	AUTHOR:		Rohit Khare
//	COPYRIGHT:	(c) 1994 California Institure of Technology, eText Project
///////////////////////////////////////////////////////////////////////////////
//	IMPLEMENTATION COMMENTS
//		Bookmark creation and destruction are incredibly fragile and complex
//	timelines, what with consistency, integrity, and bindery checking.
//		How does this coordinate with eText to find locations, etc?
//		For HTML conversion, we decided to reduce range-support to point-
//	support for bookmarks by bracketing AnchorTitle with <A NAME...>
///////////////////////////////////////////////////////////////////////////////
//	HISTORY
//	10/30/94:	Modified to support <InspectableTarget>
//	06/18/94:	Added HTMDSupport. RK & TRZ
//	05/08/94:	Created. First actual implementation.
///////////////////////////////////////////////////////////////////////////////

#import "eTBookmark.h"
#import "eTPointmark.h"
#define	_eTBookmarkVERSION	10

@implementation eTBookmark

+ toolAwake:theApp
{
	char        buf[MAXPATHLEN];
	NXBundle	*bundle;

	bundle = [NXBundle bundleForClass:[eTBookmark class]];
	if ([bundle getPath:buf forResource:".bmBegin" ofType:"tiff"] ) {
		[[[NXImage alloc] initFromFile:buf] setName:".bmBegin"];
	} else {
		NXLogError("Image not found: .bmBegin");
	}
	if ([bundle getPath:buf forResource:".bmEnd" ofType:"tiff"] ) {
		[[[NXImage alloc] initFromFile:buf] setName:".bmEnd"];
	} else {
		NXLogError("Image not found: .bmEnd");
	}
	if ([bundle getPath:buf forResource:".bmCollapsed" ofType:"tiff"] ) {
		theIcon = [[NXImage alloc] initFromFile:buf];
		[theIcon setName:".bmCollapsed"];
	} else {
		NXLogError("Image not found: .bmCollapsed");
	}
	[theApp   registerAnnotation: [eTBookmark class] 
							name: "eTBookmark"
					RTFDirective: "eTBookmark"
					   menuLabel: "Make Bookmark"
						 menuKey: 'B'
						menuIcon: (NXImage *) theIcon];
	[theApp   registerAnnotation: [eTBookmarkEnd class] 
							name: "eTBookmarkEnd"
					RTFDirective: "eTBookmarkEnd"
					   menuLabel: NULL
						 menuKey: 0
						menuIcon: (NXImage *) nil];
	[eTPointmark toolAwake:theApp]; //Chaining
	return self;
}


- init 
{
	[super init];
	etContainer = etDoc = theEnd = theText = nil;
	highlighted = collapsed = NO;
	anchorID = 0;
	anchorTitle = NXUniqueString("Untitled");
	condition = NXUniqueString("");
	theIcon = [NXImage findImageNamed:".bmBegin"];
	theTextFieldCell = [[TextFieldCell alloc] initTextCell:"Bookmark"];
	[[theTextFieldCell setBackgroundGray:NX_BLACK] setTextGray:NX_WHITE];
	return self;
}

- free
{
	// it's not clear that we can expand: recursively in a Text edit.
	//if (collapsed) NXLogError("AAAAAAAAAAAAAAAAHHHHHHHHHH!!!!!!!!!");
	[[NXApp inspector] inspect:nil];
	[[eTBookmarkBinder new] unregisterBM:self ID:anchorID inDoc:[[etDoc docInfo] docID]];
	[etDoc unregisterNotification:self];
	if (etContainer) etContainer = [etContainer free];
	if (theEnd) theEnd = [theEnd beginDidFree];
	return self = [super free];
}

- initFromPboard:thePboard inDoc:theDoc linked:(BOOL) linked
{	
	NXSelPt	begin, end;

	[self init];
	etDoc = theDoc;
	anchorID = [NXApp uniqueID];
	anchorTitle = NXUniqueString([[theDoc docInfo] docTitle]);
	[[eTBookmarkBinder new] registerBM:self ID:anchorID inDoc:[[etDoc docInfo] docID]];
	[etDoc registerNotification:self];
	
	// create bmEnd
	theEnd = [[eTBookmarkEnd alloc] init:self ID:anchorID];
	
// HIGHLY UNSCRUPULOUS KERNEL VIOLATION!!!!!	
	// move sel to end and insert bmEnd into the doc
	theText = [[theDoc docUI] eTextObj];
	[theText getSel:&begin :&end]; 	// we know the sel exists because
									// etApp checks acceptsAnnotation:
	[theText setSel:end.cp :end.cp];
	[[theText undoManager] disableUndoRegistration];
	[theDoc insertAnnotation:theEnd];
	[[theText undoManager] reenableUndoRegistration];
	
	// now move sel to the beginning and return (we will be inserted there)
	[theText setSel:begin.cp :begin.cp];
	// this provides user feedback; if another bookmark is in the Inspector
	// then this maneuver keeps the bmBrowser updated.
	[self click:self];
	return self;
}

- calcCellSize:(NXSize *)theSize 
	{[theIcon getSize:theSize]; return self;}
- drawSelf:(const NXRect *)cellFrame inView:view		// MARGINALIA HERE!
{
	NXPoint	point; NXCoord l,r,t,b; NXRect rt;
	
	if(!theText) theText = view;
    PSgsave();
	if (highlighted) PSsetgray(NX_LTGRAY);
	else PSsetgray([view backgroundGray]);
	NXRectFill(cellFrame);
    point = cellFrame->origin;
    point.y += cellFrame->size.height;
	[theIcon composite:NX_SOVER toPoint:&point];
	
	// Now we stamp the marginalia note
	// build a rect for the text
	if (theTextFieldCell) {
		NXRun *theRun = [theText runForAnnotation:self];
		rt = *cellFrame;
		
		[theTextFieldCell setFont:theRun->font];
		[theTextFieldCell setStringValue:anchorTitle];
		NX_Y(&rt) += (NX_HEIGHT(&rt) - 18.0);
		NX_HEIGHT(&rt) = 18.0;
		[theText getMarginLeft:&l right:&r top:&t bottom:&b]; 
		NX_WIDTH(&rt)  = (l - 8.0);
		NX_X(&rt) = 4.0;
		[theTextFieldCell drawSelf:&rt inView:view];
	}
	PSgrestore();
    return self;
}
- highlight:(const NXRect *)cellFrame inView:view lit:(BOOL)flag
{
    if (highlighted != flag) {
		highlighted = flag;
		NXHighlightRect(cellFrame);
    }
    return self;
}

#define mask (NX_LMOUSEUPMASK|NX_LMOUSEDRAGGEDMASK)
#define Shift(e) (e->flags&(NX_NEXTLSHIFTKEYMASK|NX_NEXTRSHIFTKEYMASK))
#ifndef abs
#define abs(x) (((x)<0)? -(x) : (x))
#endif

int eTBookmark_mouseMoved(NXPoint *o, int n) { 
	/* true if mouse moves > n pixels from 'o' */
	NXEvent *e;
	NXPoint p;
	float d;
	do {
		e = [NXApp getNextEvent:mask];
		p = e->location;
		d = abs(p.x-o->x);
		if (d < abs(p.y-o->y)) d = abs(p.y-o->y);
	} while (e->type != NX_LMOUSEUP && d<n);
	*o = p;
	return e->type != NX_LMOUSEUP;
}
- (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
{
	return (flag ? NX_DragOperationLink : NX_DragOperationAll);
}
- draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint deposited:(BOOL)flag
{
	return self;
}
- (BOOL)	trackMouse:(NXEvent *)event
			inRect:(const NXRect *)cellFrame
			ofView:controlView	
{
    NXPoint	hitPoint, preHitPoint;
    int		oldMask;
    NXEvent	saveEvent;
    Pasteboard	*dragPasteboard;
	NXPoint mouseLocation;
	
    mouseLocation = event->location;
    [controlView convertPoint:&mouseLocation fromView:NULL];
	oldMask = [[controlView window] eventMask];
    [[controlView window] setEventMask:(oldMask|mask)];
    saveEvent = *event;
    preHitPoint = event->location;
    hitPoint = event->location;

    if (NXPointInRect(&mouseLocation, cellFrame)) {
		if (event->data.mouse.click == 2)
			[self doubleClick:self];
		else if (event->data.mouse.click == 1) {
 		   if (eTBookmark_mouseMoved(&hitPoint,4)) {
			NXRect	dragRect;
			id		theTable;
			char	filename[MAXPATHLEN];
			NXPoint	imgLoc, offset;
			NXEvent *nextEvent;
					
			theTable = [[NXStringTable alloc] init];
			[theTable insertKey:DOCID value:
				NXCopyStringBuffer([[etDoc docInfo] docIDStr])];
			sprintf(filename,"%x", anchorID);
			[theTable insertKey:ANCHORID value:NXCopyStringBuffer(NXUniqueString(filename))];
			[theTable insertKey:DOCTITLE value:
				NXCopyStringBuffer([[etDoc docInfo] docTitle])];
			[theTable insertKey:ANCHORTITLE value: 
				NXCopyStringBuffer(anchorTitle)];
			sprintf(filename, "/tmp/%s.etfLink", anchorTitle);
			[theTable writeToFile:filename];
			theTable = [[theTable empty] free];

			dragPasteboard = [Pasteboard newName: NXDragPboard];
		  [dragPasteboard declareTypes:&NXFilenamePboardType num:1 owner:nil];
			[dragPasteboard writeType: NXFilenamePboardType data: filename
				length: strlen(filename)];
			
			imgLoc = saveEvent.location; 
			[controlView convertPoint:&imgLoc fromView:nil];
			imgLoc.x -= 5;
			imgLoc.y += 5;
			
			nextEvent = [NXApp currentEvent];
			offset.x = nextEvent->location.x - saveEvent.location.x;
			offset.y = nextEvent->location.y - saveEvent.location.y;
			
			[controlView
				dragImage:[NXImage findImageNamed:"NXLinkButton"]
				at:&imgLoc offset:&offset
				event:&saveEvent pasteboard:dragPasteboard
				source:self slideBack: YES];
			} else {
				[self click:self];
			}
		}
	}
	[[controlView window] setEventMask:oldMask];
	return YES;
}
			
- readRichText:(NXStream *)stream forView:view 
{
	int i;
	char buf[MAXPATHLEN];
	
	NXScanf(stream, "%d ", &i);
	if (i != _eTBookmarkVERSION) {
		// bad version block.
	 NXLogError("eTBookmark found unparseable version %d at position %d",
					i, NXTell(stream));
		return nil;
	}

	if (!theText) theText=view;
	NXScanf(stream, "%x ", &anchorID);

	NXScanf(stream, "%d", &i); NXGetc(stream); // space-eater
	if (i) NXRead(stream, buf, i);
	buf[i] = 0;
	anchorTitle = NXUniqueString(buf);

	NXScanf(stream, "%d", &i); NXGetc(stream); // space-eater
	if (i) NXRead(stream, buf, i);
	buf[i] = 0;
	condition = NXUniqueString(buf);
	
	// Register with the Binder!
	if (!etDoc) etDoc = [view etDoc];
	[[eTBookmarkBinder new] registerBM:self ID:anchorID inDoc:[[etDoc docInfo] docID]];
	[etDoc registerNotification:self];
	
	return self;
}
- writeRichText:(NXStream *)stream forView:view
{
	NXPrintf(stream, "%d %x %d %s %d %s", _eTBookmarkVERSION,anchorID, strlen(anchorTitle), anchorTitle, strlen(condition), condition);
	return self;
}
- writeASCII:(NXStream *)stream forView:view
{
	NXPrintf(stream, "%y\n", anchorTitle);
	return self;
}

- writeASCIIRef:(NXStream *)stream forView:view
{
	NXPrintf(stream, "This is anchor ID# %x", anchorID);
	return self;
}
- writeHTML:(NXStream *)stream forView:view
{
	NXPrintf(stream, "<A NAME=\"%x\"><B>%v</B></A>\n", anchorID,anchorTitle);
	return self;
}
- writeLaTeX:(NXStream *)stream forView:view
{
	NXPrintf(stream, "{\\em %w}",anchorTitle);
	return self;
}

- docWillWrite:etDoc
{
	if (collapsed){
		int	begin;
		
		begin = [theText positionForAnnotation:self];
		[theText setSel:(begin+1) :(begin+1)];
		[etContainer expand:theText];
		reclose = YES;
	}else reclose = NO;	
	return self;
}

- docDidWrite:etDoc
{
	// collapse back all the previously collapsed ones
	if (reclose) {
		int	begin,end;
		
		begin = [theText positionForAnnotation:self];
		end = [theText positionForAnnotation:theEnd];
		if (end == -1) return self;
		[theText setSel:(begin+1) :(end+1)];
		//create a container 
		if (!etContainer) etContainer = [[eTextContainer alloc] init];
		[etContainer collapse:theText];
	}
	return self;
}

- docWillOpen:etDoc
{
	// consult the userModel and collapse out conditioned bookmarks
	if (condition && *condition && ![[NXApp userModel] boolQuery:condition])
		[self collapse];
	return self;
}

- setTitle:(const char *) newTitle
{
	anchorTitle = NXUniqueString(newTitle);
	[[theText superview] display];
	return self;
}

- setCondition:(const char *) newCondition
{
	condition = NXUniqueString(newCondition);
	return self;
}

- collapse
{
	int	begin,end;
	
	begin = [theText positionForAnnotation:self];
	end = [theText positionForAnnotation:theEnd];
	if ((begin == -1) || (end == -1)) return self;
	[theText setSel:(begin+1) :(end+1)];
	[theText scrollSelToVisible];
	//create a container 
	if (!etContainer) etContainer = [[eTextContainer alloc] init];
	[etContainer collapse:theText];
	theIcon = [NXImage findImageNamed:".bmCollapsed"];
	collapsed = YES;
	highlighted = NO;
	[[theText superview] display];
	return self;
}

- expand
{
	int	begin;
	
	begin = [theText positionForAnnotation:self];
	if (begin == -1) {NXBeep(); return self;}
	[theText setSel:(begin+1) :(begin+1)];
	[etContainer expand:theText];
	theIcon = [NXImage findImageNamed:".bmBegin"];
	collapsed = NO;
	highlighted = NO;
	[self highlight:self];
	return self;
}

- click:sender
{
	[[NXApp inspector] inspect:self];
	return self;
}
- (id <Inspectable>) inspectableDelegate {
	return [[eTBookmarkUI new] setBookmark:self inDoc:[[etDoc docInfo] docID]]; }

- doubleClick:sender
{
	if (collapsed) [self expand];
	else [self collapse];
	return self;
}

- (NXAtom)title {return anchorTitle;}
- (NXAtom)condition {return condition;}
- (long)id {return anchorID;}
- setEnd:newEnd {theEnd=newEnd; return self;}

- endDidFree:sender
{
	// this is the doozie. Suicide?
	theEnd = nil;
	return self;
}

- highlight:sender
{
	int	begin,end;
	
	if (collapsed) [self expand];
	begin = [theText positionForAnnotation:self];
	if (begin == -1) {NXBeep(); return self;}
	end = [theText positionForAnnotation:theEnd];
	if (end == -1) {
		// find eoparagraph and insert an end there
		end =[theText positionFromLine:([theText lineFromPosition:begin]+1)]-1;
		if (end < begin) end = begin+1;
		// create bmEnd
		theEnd = [[eTBookmarkEnd alloc] init:self ID:anchorID];
		[theText setSel:end:end];
		[etDoc insertAnnotation:theEnd];
		end++;
	}
	//[theText display];
	[theText setSel:(begin) :(end+1)];
	[theText scrollSelToVisible];
	return self;
}

@end